package au.org.paperminer.main;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;

import org.apache.log4j.Logger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import au.org.paperminer.common.CookieHelper;
import au.org.paperminer.common.PaperMinerConstants;

import org.apache.pdfbox.*;
import org.apache.pdfbox.exceptions.COSVisitorException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDType1Font;

import java.util.zip.*;

/**
 * Servlet Filter implementation class DownloadArticleFilter
 */
public class DownloadArticleFilter implements Filter {

    private Logger m_logger;

	/**
     * Default constructor. 
     */
    public DownloadArticleFilter() {
        m_logger = Logger.getLogger(PaperMinerConstants.LOGGER);
    }

	/**
	 * @see Filter#destroy()
	 */
	public void destroy() {
		// TODO Auto-generated method stub
	}

	/**
	 * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
	 */
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
		String[] troveIds = (request.getParameter("troveIds")).split(",");
		String downloadType = request.getParameter("downloadType").toString();
		if(troveIds.length == 1 && troveIds.length != 0){
			if(downloadType.equals("pdf"))
			{
				AddPDFToResponse(request, response, troveIds[0]);
			}
			else if(downloadType.equals("txt"))
			{
				AddTXTToResponse(request, response, troveIds[0]);
			}
		}
		else{
			if(downloadType.equals("pdf"))
			{
				AddPDFZIPFileToResponse(request, response, troveIds);
			}
			else if(downloadType.equals("txt"))
			{
				AddTXTZIPFileToResponse(request, response, troveIds);
			}
		}
		
		chain.doFilter(request, response);
	}
	
	/**q
	 * @see Filter#init(FilterConfig)
	 */
	public void init(FilterConfig fConfig) throws ServletException {
		// TODO Auto-generated method stub
	}
	
	private void AddPDFToResponse(ServletRequest request, ServletResponse response, String troveId)
	{
        try {
	        JSONObject obj = GetJSONObjectForTroveId(request, troveId);
	        
	        HttpServletResponse res = (HttpServletResponse)response;
	        res.setContentType("application/pdf");
	        res.setHeader("Content-Disposition", "attachment; filename="+
	        		((JSONObject)((JSONObject)obj.get("article")).get("title")).get("value").toString()
	        		.replaceAll("[^a-zA-Z0-9.-]", "")+".pdf");
	        ServletOutputStream out = res.getOutputStream();
	        
			PDDocument document = CreatePDF(obj);
			document.save(out);
	    }
        catch(Exception e){
        	m_logger.info(e);
        	throw new RuntimeException(e);
        }
	}
	
	private void AddTXTToResponse(ServletRequest request, ServletResponse response, String troveId)
	{
		try {
			JSONObject obj = GetJSONObjectForTroveId(request, troveId);
	    
		    HttpServletResponse res = (HttpServletResponse)response;
		    res.setContentType("text/plain");
		    res.setHeader("Content-Disposition", "attachment; filename="+
		    		((JSONObject)((JSONObject)obj.get("article")).get("title")).get("value").toString()
		    		.replaceAll("[^a-zA-Z0-9.-]", "")+".txt");
		    ServletOutputStream out = res.getOutputStream();

	        String text = GetTextFileString(obj);

	        byte[] bytes = text.getBytes();
	        out.write(bytes);
		}
        catch(Exception e){
        	m_logger.info(e);
        	throw new RuntimeException(e);
        }
	}
	
	private void AddPDFZIPFileToResponse(ServletRequest request, ServletResponse response, String[] troveIds)
	{
        try {
	        HttpServletResponse res = (HttpServletResponse)response;
	        res.setContentType("application/zip");
	        res.setHeader("Content-Disposition", "attachment; filename=articles.zip");
	        ServletOutputStream out = res.getOutputStream();
			ZipOutputStream zipOutStream = new ZipOutputStream(out);
						
	        for(int i=0; i < troveIds.length; i++)
			{
	        	PDDocument document = null;
	        	String PDFName = null;
	        	try{
			        JSONObject obj = GetJSONObjectForTroveId(request, troveIds[i]);
			        
					document = CreatePDF(obj);
					PDFName = ((JSONObject)((JSONObject)obj.get("article")).get("title"))
							.get("value").toString().replaceAll("[^a-zA-Z0-9.-]", "")
		        			+ "-" + ((JSONObject)obj.get("article")).get("id").toString()+".pdf";
	        	}
	        	catch(Exception e){
	            	m_logger.info(e);
	        	}
	        	
	        	if(document != null && PDFName != null){
		        	zipOutStream.putNextEntry(new ZipEntry(PDFName));
		        	
		        	ByteArrayOutputStream bOut = new ByteArrayOutputStream();
		        	document.save(bOut);
		        	byte[] pdfData = bOut.toByteArray();
		        	
		        	zipOutStream.write(pdfData);
					zipOutStream.closeEntry();
	        	}
			}
			zipOutStream.close();
	    }
        catch(Exception e){
        	m_logger.info(e);
        	throw new RuntimeException(e);
        }
	}
	
	private void AddTXTZIPFileToResponse(ServletRequest request, ServletResponse response, String[] troveIds)
	{
        try {
	        HttpServletResponse res = (HttpServletResponse)response;
	        res.setContentType("application/zip");
	        res.setHeader("Content-Disposition", "attachment; filename=articles.zip");
	        ServletOutputStream out = res.getOutputStream();
			ZipOutputStream zipOutStream = new ZipOutputStream(out);
					
	        for(int i=0; i < troveIds.length; i++)
			{
	        	String TXTName = null;
	        	String text = null;
	        	try{
			        JSONObject obj = GetJSONObjectForTroveId(request, troveIds[i]);
			        
					TXTName = ((JSONObject)((JSONObject)obj.get("article")).get("title"))
							.get("value").toString().replaceAll("[^a-zA-Z0-9.-]", "")
		        			+ "-" + ((JSONObject)obj.get("article")).get("id").toString()+".txt";
	
			        text = GetTextFileString(obj);
	        	}
	        	catch(Exception e){
	            	m_logger.info(e);
	        	}
	        	
	        	if(TXTName != null && text != null){
		        	zipOutStream.putNextEntry(new ZipEntry(TXTName));
	
			        byte[] bytes = text.getBytes();
		        	
		        	zipOutStream.write(bytes);
					zipOutStream.closeEntry();
	        	}
			}
			zipOutStream.close();
	    }
        catch(Exception e){
        	m_logger.info(e);
        	throw new RuntimeException(e);
        }
	}

	private PDDocument CreatePDF(JSONObject obj) throws Exception{
		PDDocument document = new PDDocument();
		PDPage page = new PDPage();
		document.addPage( page );

		// Create a new font object selecting one of the PDF base fonts
		PDFont font = PDType1Font.HELVETICA_BOLD;

		// Start a new content stream which will "hold" the to be created content
		PDPageContentStream contentStream = new PDPageContentStream(document, page);

		// Define a text content stream using the selected font, moving the cursor and drawing the text "Hello World"
		contentStream.beginText();

		int fontSize = 12;
		int lineWidth = 900;
		contentStream.setFont( font, fontSize );
	    contentStream.moveTextPositionByAmount(100, 700);
	    
	    JSONObject article = ((JSONObject)obj.get("article"));
	    
	    contentStream.drawString("Zone: newspaper");
	    contentStream.moveTextPositionByAmount(0, -15);
	    contentStream.drawString("ID: " + article.get("id").toString());
	    contentStream.moveTextPositionByAmount(0, -15);
	    contentStream.drawString("Date: " + article.get("date").toString());
	    contentStream.moveTextPositionByAmount(0, -15);
	    contentStream.drawString("Source: " + ((JSONObject)article.get("title"))
	    		.get("value").toString());
	    contentStream.moveTextPositionByAmount(0, -15);
	    contentStream.drawString("Category: " + article.get("category").toString());
	    contentStream.moveTextPositionByAmount(0, -15);
	    contentStream.drawString("Page: " + article.get("page").toString());
	    contentStream.moveTextPositionByAmount(0, -15);
		
		float fontWidth = font.getFontDescriptor().getFontBoundingBox().getWidth()/ 1000 * fontSize;
		float fontHieght = font.getFontDescriptor().getFontBoundingBox().getWidth()/ 1000 * fontSize;
		 // Or whatever font size you want.
		String text = (article.get("articleText").toString()).replaceAll("\\<.*?\\>", "");

		List<String> lines = SeparateStringIntoLines(text, fontHieght, 
				fontWidth, lineWidth);

		contentStream.moveTextPositionByAmount(0, -30);

		for ( String line : lines ) {
		    contentStream.drawString(line);
		    contentStream.moveTextPositionByAmount(0, -15);
		}
		
		//contentStream.drawString( obj.get("article").toString() );
		contentStream.endText();

		// Make sure that the content stream is closed:
		contentStream.close();
		
		return document;
	}
	
	private List<String> SeparateStringIntoLines(String text, float fontHieght, float fontWidth, int lineWidth){
		List<String> lines = new ArrayList<String>();

		String[] strArr = text.split(" ");
		ArrayList<Integer> widthInt = new ArrayList<Integer>();
		int tempLineWidth = 0;
		for(int i = 0; i < strArr.length; i++){
			tempLineWidth += strArr[i].length() + 1;
			if(i < strArr.length-1){
				if(tempLineWidth*fontWidth >= lineWidth){
					widthInt.add(i-1);
					tempLineWidth = 0;
				}
			}
			else{
				widthInt.add(i);
				break;
			}
		}
		
		for(int i : widthInt){

	    	m_logger.info(i);
		}

    	m_logger.info(strArr.length);
		
		String lineText = "";
		int k = 0;
		int nextSplit = widthInt.get(k);
		for(int i = 0; i < strArr.length; i++){
			lineText += strArr[i] + " ";
			if(i == strArr.length-1){
				lines.add(lineText);
			}
			else if(i == nextSplit){
				lines.add(lineText);
				lineText = "";
				k++;
				nextSplit = widthInt.get(k);
			}
		}
		
		return lines;
	}
	
	private JSONObject GetJSONObjectForTroveId(ServletRequest request, String troveId){
		URL url = null;
        HttpURLConnection con = null;
        try {
			String key = CookieHelper.getCookieValue((HttpServletRequest) request, 
					PaperMinerConstants.PMC_TROVE_KEY);
	        url = new URL("http://api.trove.nla.gov.au/newspaper/" + troveId + "?key=" + 
					key + "&reclevel=full&encoding=json&include=articleText");
	        con = (HttpURLConnection) url.openConnection();
	        con.setRequestMethod("GET");
	        
	        InputStream is = con.getInputStream();
	        BufferedReader rd = new BufferedReader(new InputStreamReader(is, Charset.forName("UTF-8")));
	        
	        JSONParser parser=new JSONParser();
	        JSONObject obj = (JSONObject) parser.parse(rd);
	        
	        return obj;
        }
        catch(Exception e){
        	m_logger.info(e);
        	throw new RuntimeException(e);
        }
	}
	
	private String GetTextFileString(JSONObject obj){
	    JSONObject article = ((JSONObject)obj.get("article"));
		String fileText = "Zone: newspaper"
				+ "\nID: " + article.get("id").toString()
				+ "\nID: " + article.get("id").toString()
				+ "\nDate: " + article.get("date").toString()
				+ "\nSource: " + ((JSONObject)article.get("title")).get("value").toString()
				+ "\nCategory: " + article.get("category").toString()
				+ "\nPage: " + article.get("page").toString()
				+ "\nPage: " + article.get("page").toString()
				+ "\n\n" + (article.get("articleText").toString()).replaceAll("\\<.*?\\>", "");
		
		return fileText;		
	}
}
